﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;

namespace Heaven.Texturing.Importers.DirectDraw
{
    public class CubeMap
    {
        #region Атрибуты

        // Грань - положительная X. Массив всех текстур. От основной к мипмапам по уменьшению размера
        private RawImage[] positiveX = null;
        // Грань - положительная Y. Массив всех текстур. От основной к мипмапам по уменьшению размера
        private RawImage[] positiveY = null;
        // Грань - положительная Z. Массив всех текстур. От основной к мипмапам по уменьшению размера
        private RawImage[] positiveZ = null;

        // Грань - отрицательная X. Массив всех текстур. От основной к мипмапам по уменьшению размера
        private RawImage[] negativeX = null;
        // Грань - отрицательная Y. Массив всех текстур. От основной к мипмапам по уменьшению размера
        private RawImage[] negativeY = null;
        // Грань - отрицательная Z. Массив всех текстур. От основной к мипмапам по уменьшению размера
        private RawImage[] negativeZ = null;

        // Ширина
        private int width;
        // Высота
        private int height;
        // Колв-о мипов
        private int mipMapsCount;

        // Формат пикселей
        private string pixelFormat;

        #endregion

        #region Свойства

        /// <summary>
        /// Формат пикселей
        /// </summary>
        public string PixelFormat
        {
            get { return pixelFormat; }
        }

        /// <summary>
        /// Грань - положительная X. Массив всех текстур. От основной к мипмапам по уменьшению размера
        /// </summary>
        public RawImage[] PositiveX
        {
            get { return positiveX; }
        }

        /// <summary>
        /// Грань - положительная Y. Массив всех текстур. От основной к мипмапам по уменьшению размера
        /// </summary>
        public RawImage[] PositiveY
        {
            get { return positiveY; }
        }

        /// <summary>
        /// Грань - положительная Z. Массив всех текстур. От основной к мипмапам по уменьшению размера
        /// </summary>
        public RawImage[] PositiveZ
        {
            get { return positiveZ; }
        }

        /// <summary>
        /// Грань - отрицательная X. Массив всех текстур. От основной к мипмапам по уменьшению размера
        /// </summary>
        public RawImage[] NegativeX
        {
            get { return negativeX; }
        }

        /// <summary>
        /// Грань - отрицательная Y. Массив всех текстур. От основной к мипмапам по уменьшению размера
        /// </summary>
        public RawImage[] NegativeY
        {
            get { return negativeY; }
        }

        /// <summary>
        /// Грань - отрицательная Z. Массив всех текстур. От основной к мипмапам по уменьшению размера
        /// </summary>
        public RawImage[] NegativeZ
        {
            get { return negativeZ; }
        }

        /// <summary>
        /// Ширина
        /// </summary>
        public int Width
        {
            get { return width; }
        }

        /// <summary>
        /// Высота
        /// </summary>
        public int Height
        {
            get { return height; }
        }

        /// <summary>
        /// Кол-во мипов
        /// </summary>
        public int MipMapCount
        {
            get { return mipMapsCount; }
        }

        #endregion

        #region Инициализация

        /// <summary>
        /// Конструктор
        /// </summary>
        /// <param name="header">Заголовок</param>
        /// <param name="reader">Ридер с данными</param>
        internal CubeMap(Header header, BinaryReader reader)
        {
            // Извлекаем нужные данные из заголовка
            width = header.Width;
            height = header.Height;
            bool hasMipMaps = (((header.Flags & Header.DDSD_MIPMAPCOUNT)!=0)&&((header.Caps.Caps1 & Header.DDSCAPS_MIPMAP)!=0)&&((header.Caps.Caps1 & Header.DDSCAPS_COMPLEX)!=0));
            mipMapsCount = 0;
            if (hasMipMaps) mipMapsCount = header.MipMapCount-1;
            // Положительный X
            if ((header.Caps.Caps2 & Header.DDSCAPS2_CUBEMAP_POSITIVEX) != 0) positiveX = LoadTexture(header, reader);
            // Отрицательный X
            if ((header.Caps.Caps2 & Header.DDSCAPS2_CUBEMAP_NEGATIVEX) != 0) negativeX = LoadTexture(header, reader);
            // Положительный Y
            if ((header.Caps.Caps2 & Header.DDSCAPS2_CUBEMAP_POSITIVEY) != 0) positiveY = LoadTexture(header, reader);
            // Отрицательный Y
            if ((header.Caps.Caps2 & Header.DDSCAPS2_CUBEMAP_NEGATIVEY) != 0) negativeY = LoadTexture(header, reader);
            // Положительный Z
            if ((header.Caps.Caps2 & Header.DDSCAPS2_CUBEMAP_POSITIVEZ) != 0) positiveZ = LoadTexture(header, reader);
            // Отрицательный Z
            if ((header.Caps.Caps2 & Header.DDSCAPS2_CUBEMAP_NEGATIVEZ) != 0) negativeZ = LoadTexture(header, reader);
        }

        #endregion   

        #region Methods

        /*/// <summary>
        /// Uploads data to graphics device 
        /// and gives interface to access it
        /// </summary>
        /// <returns>Uploaded cubic texture</returns>
        public ITextureCube Upload(IDevice device)
        {
            ITextureCube cubemap = device.TextureCreator.CreateCube(Width, SurfaceFormat.Rgba32);
            cubemap.PositiveX.Buffer.Upload(PositiveX[0].Data);
            cubemap.PositiveY.Buffer.Upload(PositiveY[0].Data);
            cubemap.PositiveZ.Buffer.Upload(PositiveZ[0].Data);
            cubemap.NegativeX.Buffer.Upload(NegativeX[0].Data);
            cubemap.NegativeY.Buffer.Upload(NegativeY[0].Data);
            cubemap.NegativeZ.Buffer.Upload(NegativeZ[0].Data);

            return cubemap;
        }*/

        #endregion

        #region Внутренее

        // Загрузка текстуры с мипмапами для одной из граней
        private RawImage[] LoadTexture(Header header, BinaryReader reader)
        {
            RawImage[] textures = new RawImage[mipMapsCount+1];
            int currentWidth = width;
            int currentHeight = Height;
            // Определем тип компресии
            if ((header.PixelFormat.Flags & Header.DDPF_FOURCC) == 0)
            {
                // Без компресии
                // Каждый следующий мипмап в два раза меньше предыдущего
                // Определяем формат пикселя
                pixelFormat = "";
                if ((header.PixelFormat.Flags & Header.DDPF_RGB) != 0) pixelFormat = "RGB";
                else if ((header.PixelFormat.Flags & Header.DDPF_LUMINANCE) != 0) pixelFormat = "L";
                else if ((header.PixelFormat.Flags & Header.DDPF_ALPHA) != 0) pixelFormat = "A";
                else if ((header.PixelFormat.Flags & Header.DDPF_YUV) != 0) pixelFormat = "YUV";
                else if (((header.PixelFormat.Flags & Header.DDPF_PALETTEINDEXED1) != 0) || ((header.PixelFormat.Flags & Header.DDPF_PALETTEINDEXED2) != 0) || ((header.PixelFormat.Flags & Header.DDPF_PALETTEINDEXED4) != 0) || ((header.PixelFormat.Flags & Header.DDPF_PALETTEINDEXED8) != 0)) pixelFormat = "P";

                if ((header.PixelFormat.Flags & Header.DDPF_ALPHAPIXELS) != 0) pixelFormat += "A";
                if (pixelFormat.Length == 0) pixelFormat = "Unknown";
                pixelFormat += header.PixelFormat.RgbBitCount;
                // Проверяем наличие питча
                int pitch = header.PitchOrLinearSize;
                if ((header.Flags & Header.DDSD_PITCH) == 0) pitch = width * header.PixelFormat.RgbBitCount / 8;
                // Грузим основную карту
                textures[0] = new RawImage(currentWidth, currentHeight, reader.ReadBytes(pitch * currentHeight));
                // Грузим мипы
                for(int i=1;i<=mipMapsCount;i++)
                {
                    currentWidth /= 2;
                    currentHeight /= 2;
                    if (currentWidth == 0) currentWidth = 1;
                    if (currentHeight == 0) currentHeight = 1;
                    pitch = currentWidth * header.PixelFormat.RgbBitCount / 8;// Могут быть проблемы с двуцветными изображениями(если такие есть)
                    textures[i] = new RawImage(currentWidth, currentHeight, reader.ReadBytes(pitch * currentHeight));
                }
            }
            else
            {
                // С компресией
                // Каждый следующий мипмап занимает в два раза меньше места
                // Проверяем наличие линейного размера
                int minimumSize = 8;
                if (((header.PixelFormat.FourCC >> 24) & 0x000000FF) > (int)'1') minimumSize = 16;
                int linearSize = header.PitchOrLinearSize;
                if ((header.Flags & Header.DDSD_LINEARSIZE) == 0)
                {
                    linearSize = width * height * minimumSize;
                }        
                pixelFormat = "";
                pixelFormat += new string((char)((header.PixelFormat.FourCC) & 0x000000FF), 1);
                pixelFormat += new string((char)((header.PixelFormat.FourCC >> 8) & 0x000000FF), 1);
                pixelFormat += new string((char)((header.PixelFormat.FourCC >> 16) & 0x000000FF), 1);
                pixelFormat += new string((char)((header.PixelFormat.FourCC >> 24) & 0x000000FF), 1);                                
                // Грузим основную карту
                textures[0] = new RawImage(currentWidth, currentHeight, reader.ReadBytes(linearSize));
                // Грузим мипы
                for (int i = 1; i <= mipMapsCount; i++)
                {
                    currentWidth /= 2;
                    currentHeight /= 2;
                    if (currentWidth == 0) currentWidth = 1;
                    if (currentHeight == 0) currentHeight = 1;
                    if (currentWidth == currentHeight) linearSize /= 2;
                    else linearSize = currentWidth * currentHeight * minimumSize;
                    if (linearSize < minimumSize) linearSize = minimumSize;
                    textures[i] = new RawImage(currentWidth, currentHeight, reader.ReadBytes(linearSize));
                }
            }            
            return textures;
        }

        #endregion
    }
}
